#ifndef __EXPLORER_ENGINE_H__
#define __EXPLORER_ENGINE_H__

////////////////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////////////////

#include "Point2D.hpp"
#include "Vector2D.hpp"
#include <vector>
#include "Structures.hpp"

////////////////////////////////////////////////////////////////////////////////
// Namespaces
////////////////////////////////////////////////////////////////////////////////

namespace SC {

////////////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////////////

/// Body type
template <typename P, typename D>
typedef struct LEBody {

	/// Default constructor
	LEBody() : neighbours(NULL) {}

	/// Body location
	Types::Point2D<P> location;

	/// Body displacement vector
	Types::Vector2D<P> disp;

	/// Body's size
	float size;

	/// Body neighbours (bodies with same parent). This list should be maintained outside.
	std::vector<unsigned int> * neighbours;

	/// Data
	D data;

};

/// Explorer engine
class ExplorerEngine {

	public:

		/// Default constructor
		ExplorerEngine()
		: threshold(0.00001), margin(0.1), body_scale(1.), rep_strength(0.025), att_strength(0.1)
		{}

		/// Sets the bodies list
		void SetBodies(const std::vector< LEBody<LEB_P,LEB_D> > & value) { this->bodies = value; }

		/// Returns the number of bodies
		const unsigned int GetNbBodies() const { return this->bodies.size(); }

		/// Returns the bodies (read-only)
		const std::vector< LEBody<LEB_P,LEB_D> > & GetBodies() const { return this->bodies; }

		/// Returns the bodies
		std::vector< LEBody<LEB_P,LEB_D> > & Bodies() { return this->bodies; }

		/// Add a body and return its index
		const unsigned int AddBody();

		/// Returns the required body (read-only)
		const LEBody<LEB_P,LEB_D> & GetBody(const unsigned int idx) const { return this->bodies[idx]; }

		/// Returns the required body
		LEBody<LEB_P,LEB_D> & Body(const unsigned int idx) { return this->bodies[idx]; }

		/// Removes the body at the given index
		void RemoveBody(const unsigned int idx) { this->bodies.erase(this->bodies.begin()+idx); }

		/// Updates bodies location
		const bool UpdateBodies(float &);

		/// Returns the margin (read-only)
		const double GetMargin() const { return this->margin; }

		/// Returns the margin
		double & Margin() { return this->margin; }

		/// Scales the size of the body with respect to their initial size
		void SetBodyScale(const double);

		/// Returns the body scale
		const double GetBodyScale() const { return this->body_scale; }

		/// Returns the repulsion strength (read-only)
		const double GetRepulsion() const { return this->rep_strength; }

		/// Returns the repulsion strength
		double & Repulsion() { return this->rep_strength; }

		/// Returns the attraction strength (read-only)
		const double GetAttraction() const { return this->att_strength; }

		/// Returns the attraction strength
		double & Attraction() { return this->att_strength; }

		/// Attract the given bodies towards their isobarycentre with teh given strength
		void ApplyAttraction(const std::vector<unsigned int> &, const bool);

		/// Clears engine
		void Clear() { this->bodies.clear(); this->body_scale = 1.; }

	protected:

		/// List of bodies in the system
		std::vector< LEBody<LEB_P,LEB_D> > bodies;

		/// Motion threshold
		double threshold;

		/// Margin betwwen bodies
		double margin;

		/// Body scale (used to scale body sizes from a reference scale)
		double body_scale;

		/// Repulsion strength
		double rep_strength;

		/// Attraction strength
		double att_strength;

		/// Returns the repulsion effect of the second body on the first
		inline const Types::Vector2D<LEB_P> repulsion(const LEBody<LEB_P,LEB_D> &, const LEBody<LEB_P,LEB_D> &) const;

};

////////////////////////////////////////////////////////////////////////////////
// Closing Namespaces
////////////////////////////////////////////////////////////////////////////////

}

////////////////////////////////////////////////////////////////////////////////

#endif
